package websocket;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Handshake
{
    private final static Logger LOGGER = Logger.getLogger(Handshake.class
        .getName());
    
    private String host;
    private String origin;
    private String uri;
    private String websocketKey;
    private String websocketProtocol;
    private String websocketExtensions;
    private int websocketVersion;
    
    public Handshake (HttpRequest handshakeRequest)
    {
        // Check if request is valid
        if ( ! isValidWebsocketHandshakeRequest(handshakeRequest))
        {
            throw new IllegalArgumentException("Passed request is not a valid websocket handshake request");
        }
        
        // Parse it / read the data
        host = handshakeRequest.getHeader("Host");
        origin = handshakeRequest.getHeader("Origin");
        websocketKey = handshakeRequest.getHeader("Sec-WebSocket-Key");
        websocketProtocol = handshakeRequest.getHeader("Sec-WebSocket-Protocol");
        
        uri = handshakeRequest.getRequestedURI();
        
        try
        {
            websocketVersion = Integer.parseInt(handshakeRequest.getHeader("Sec-WebSocket-Version"));
        }
        catch (NumberFormatException ex)
        {
            throw new IllegalArgumentException("Passed request is not valid. Sec-WebSocket-Version is not an integer");
        }
    }

    public void doHandshake(Socket client) throws IOException
    {
        // Generate accept key
        String acceptKey = generateAcceptKey(websocketKey);
        
        // Generate HTTP response
        HttpResponse response = new HttpResponse(HttpResponse.StatusCode.SWITCHING_PROTOCOLS_101);
        response.setHeader("Upgrade", "websocket");
        response.setHeader("Connection", "Upgrade");
        response.setHeader("Sec-WebSocket-Accept", acceptKey);
        String message = response.getResponse();
        
        LOGGER.log(Level.FINER, "Generated HTTP handshake response:\n{0}", message);
        
        // Send HTTP handshake response
        try
        {
            PrintWriter out = new PrintWriter(client.getOutputStream());
            out.print(message);
            out.flush();
        }
        catch (IOException ex)
        {
            LOGGER.log(Level.INFO, "Connection with client {0}:{1} is probably lost. Handshake wasn't finished.",
                                   new Object[]{client.getInetAddress(), client.getPort()});
            throw ex;
        }
    }
    
    public String getHost()
    {
        return host;
    }

    public String getOrigin()
    {
        return origin;
    }

    public String getUri()
    {
        return uri;
    }

    public String getWebsocketProtocol()
    {
        return websocketProtocol;
    }

    public String getWebsocketExtensions()
    {
        return websocketExtensions;
    }

    public int getWebsocketVersion()
    {
        return websocketVersion;
    }
    
    public static boolean isValidWebsocketHandshakeRequest(HttpRequest request)
    {
        // TODO [If you want] We can also check if the request method is GET and HTTP version is at least 1.1 (according to RFC)
        return "websocket".equalsIgnoreCase(request.getHeader("Upgrade"))
                && request.containsHeader("Sec-WebSocket-Key")
                && request.containsHeader("Sec-WebSocket-Version"); // We can also make it to verify if version is at least 13
        // && headers.containsKey("Origin") // It is defined as required in RFC only when request comes from a browser
    }
    
    public static String generateAcceptKey(String websocketKey)
    {
        // Append the magic string
        websocketKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

        try
        {
            // Encode with SHA1
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] sha1Encoded = md.digest(websocketKey.getBytes());
            
            // Encode with Base64
            return Base64.encode(sha1Encoded);
        }
        catch (NoSuchAlgorithmException ex)
        {
            // This error should never come out though
            LOGGER.severe(ex.toString());
            return "";
        }
    }
}
